﻿using IslandUnit.Providers;
using System.Collections.Generic;
using System.Linq.Expressions;
using System;

namespace IslandUnit
{
    /// <summary>
    /// Main class, used to initialize the mocked object.
    /// </summary>
    /// <typeparam name="TDependency">The type of the dependency.</typeparam>
    public class Dependency<TDependency> : ICreateIsolation<TDependency>, IMockVerification, IHideObjectMembers, IDependencySetupFlow where TDependency : class
    {
        private readonly IList<MockSetup<TDependency>> _mockSetups = new List<MockSetup<TDependency>>();

        internal Isolator Isolator { get; private set; }
        
        internal object Mock { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="Dependency&lt;TDependency&gt;"/> class.
        /// The mock object will be created here.
        /// </summary>
        internal Dependency(Isolator isolator)
        {
            this.Isolator = isolator;
            this.Mock = ProviderRepository.Instance().MockProvider.CreateMockObject<TDependency>();

            // Put the object in container
            var mockedObject = ProviderRepository.Instance().MockProvider.GetMockObject<TDependency>(this.Mock);
            ProviderRepository.Instance().ContainerProvider.Bind(this.Isolator.Container, mockedObject);
        }

        /// <summary>
        /// Returns the mocked dependency
        /// </summary>
        /// <returns>The mocked object.</returns>
        internal TDependency Object
        {
            get { return ProviderRepository.Instance().MockProvider.GetMockObject<TDependency>(this.Mock); }
        }

        internal IEnumerable<MockSetup<TDependency>> MockSetups
        {
            get
            {
                return this._mockSetups;
            }
        }

        #region ICreateIsolation<TDependency> members

        /// <summary>
        /// Creates an stub for mocked object, responsible to
        /// provide fake data to unit tests.
        /// </summary>
        /// <param name="method">The target function of the stub.</param>
        /// <returns>An <see cref="StubItemSetup&lt;TDependency, TResult&gt;"/>, where you will define the stub behavior</returns>
        public StubItemSetup<TDependency, TResult> WithStubFor<TResult>(Expression<Func<TDependency, TResult>> method)
        {
            var stubSetup = new StubItemSetup<TDependency, TResult>(this, method);

            return stubSetup;
        }

        /// <summary>
        /// Creates an stub for mocked object, responsible to
        /// provide fake data to unit tests.
        /// </summary>
        /// <param name="method">The target function of the stub.</param>
        /// <returns>An <see cref="StubItemSetup&lt;TDependency, TResult&gt;"/>, where you will define the stub behavior</returns>
        public StubListSetup<TDependency, TResult> WithStubFor<TResult>(Expression<Func<TDependency, IList<TResult>>> method)
        {
            var stubListSetup = new StubListSetup<TDependency, TResult>(this, method);

            return stubListSetup;
        }

        /// <summary>
        /// Creates an stub for mocked object, responsible to
        /// provide fake data to unit tests.
        /// </summary>
        /// <param name="method">The target function of the stub.</param>
        /// <returns>An <see cref="StubItemSetup&lt;TDependency, TResult&gt;"/>, where you will define the stub behavior</returns>
        public StubEnumerableSetup<TDependency, TResult> WithStubFor<TResult>(Expression<Func<TDependency, IEnumerable<TResult>>> method)
        {
            var stubEnumerableSetup = new StubEnumerableSetup<TDependency, TResult>(this, method);

            return stubEnumerableSetup;
        }

        /// <summary>
        /// Creates an mock for mocked object, responsible to
        /// provide instrumentation to methods in unit tests.
        /// </summary>
        /// <param name="method">The target function of the mock.</param>
        /// <returns>An <see cref="MockSetup&lt;TDependency&gt;"/>, where you will define the mock behavior</returns>
        public MockSetup<TDependency> WithMockFor(Expression<Func<TDependency, object>> method)
        {
            var mockSetup = new MockSetup<TDependency>(this, method);
            this._mockSetups.Add(mockSetup);

            return mockSetup;
        }

        /// <summary>
        /// Creates an mock for mocked object, responsible to
        /// provide instrumentation to methods in unit tests.
        /// </summary>
        /// <param name="method">The target procedure of the mock.</param>
        /// <returns>An <see cref="MockSetup&lt;TDependency&gt;"/>, where you will define the mock behavior.</returns>
        public MockSetup<TDependency> WithMockFor(Expression<Action<TDependency>> method)
        {
            var mockSetup = new MockSetup<TDependency>(this, method);
            this._mockSetups.Add(mockSetup);

            return mockSetup;
        }

        #endregion

        /// <summary>
        /// Returns to the <see cref="Isolator"/>, to
        /// permit the flow of fluent interface.
        /// </summary>
        /// <returns>The <see cref="Isolator"/> of your dependency.</returns>
        public IDependencySetup Then()
        {
            return this.Isolator;
        }

        /// <summary>
        /// Verifies all the mock expectations setted up in the
        /// dependency using <see cref="MockSetup&lt;TDependency&gt;"/>.
        /// Should be called in "Assert" region of your unit tests.
        /// </summary>
        public void VerifyMockExpectations()
        {
            ProviderRepository.Instance().MockProvider.VerifyMockExpectations(this.MockSetups);
        }
    }
}
